home *** CD-ROM | disk | FTP | other *** search
/ Java Programmer's Toolkit / Java Programmer's Toolkit.iso / applets / animator / animat~1.jav < prev    next >
Text File  |  1995-10-31  |  23KB  |  910 lines

  1. /*
  2.  * @(#)Animator.java    1.3 95/10/05 Herb Jellinek
  3.  *
  4.  * Copyright (c) 1994-1995 Sun Microsystems, Inc. All Rights Reserved.
  5.  *
  6.  * Permission to use, copy, modify, and distribute this software
  7.  * and its documentation for NON-COMMERCIAL or COMMERCIAL purposes and
  8.  * without fee is hereby granted. 
  9.  * Please refer to the file http://java.sun.com/copy_trademarks.html
  10.  * for further important copyright and trademark information and to
  11.  * http://java.sun.com/licensing.html for further important licensing
  12.  * information for the Java (tm) Technology.
  13.  * 
  14.  * SUN MAKES NO REPRESENTATIONS OR WARRANTIES ABOUT THE SUITABILITY OF
  15.  * THE SOFTWARE, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
  16.  * TO THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
  17.  * PARTICULAR PURPOSE, OR NON-INFRINGEMENT. SUN SHALL NOT BE LIABLE FOR
  18.  * ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR
  19.  * DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES.
  20.  * 
  21.  * THIS SOFTWARE IS NOT DESIGNED OR INTENDED FOR USE OR RESALE AS ON-LINE
  22.  * CONTROL EQUIPMENT IN HAZARDOUS ENVIRONMENTS REQUIRING FAIL-SAFE
  23.  * PERFORMANCE, SUCH AS IN THE OPERATION OF NUCLEAR FACILITIES, AIRCRAFT
  24.  * NAVIGATION OR COMMUNICATION SYSTEMS, AIR TRAFFIC CONTROL, DIRECT LIFE
  25.  * SUPPORT MACHINES, OR WEAPONS SYSTEMS, IN WHICH THE FAILURE OF THE
  26.  * SOFTWARE COULD LEAD DIRECTLY TO DEATH, PERSONAL INJURY, OR SEVERE
  27.  * PHYSICAL OR ENVIRONMENTAL DAMAGE ("HIGH RISK ACTIVITIES").  SUN
  28.  * SPECIFICALLY DISCLAIMS ANY EXPRESS OR IMPLIED WARRANTY OF FITNESS FOR
  29.  * HIGH RISK ACTIVITIES.
  30.  */
  31.  
  32. import java.io.InputStream;
  33. import java.awt.*;
  34. import java.awt.image.ImageProducer;
  35. import java.applet.Applet;
  36. import java.applet.AudioClip;
  37. import java.util.Vector;
  38. import java.util.Hashtable;
  39. import java.util.Enumeration;
  40. import java.io.File;
  41. import java.net.URL;
  42. import java.net.MalformedURLException;
  43.  
  44. /**
  45.  * An applet that plays a sequence of images, as a loop or a one-shot.
  46.  * Can have a soundtrack and/or sound effects tied to individual frames.
  47.  *
  48.  * @author Herb Jellinek
  49.  * @version 1.1, 08 Sep 1995
  50.  */
  51.  
  52. public class Animator extends Applet implements Runnable {
  53.     
  54.     /**
  55.      * The images, in display order (Images).
  56.      */
  57.     Vector images = null;
  58.  
  59.     /**
  60.      * Duration of each image (Integers, in milliseconds).
  61.      */
  62.     Hashtable durations = null;
  63.  
  64.     /**
  65.      * Sound effects for each image (AudioClips).
  66.      */
  67.     Hashtable sounds = null;
  68.  
  69.     /**
  70.      * Position of each image (Points).
  71.      */
  72.     Hashtable positions = null;
  73.  
  74.     /**
  75.      * Background image URL, if any.
  76.      */
  77.     URL backgroundImageURL = null;
  78.  
  79.     /**
  80.      * Background image, if any.
  81.      */
  82.     Image backgroundImage = null;
  83.  
  84.     /**
  85.      * Start-up image URL, if any.
  86.      */
  87.     URL startUpImageURL = null;
  88.  
  89.     /**
  90.      * Start-up image, if any.
  91.      */
  92.     Image startUpImage = null;
  93.  
  94.     /**
  95.      * The soundtrack's URL.
  96.      */
  97.     URL soundtrackURL = null;
  98.  
  99.     /**
  100.      * The soundtrack.
  101.      */
  102.     AudioClip soundtrack;
  103.  
  104.     /**
  105.      * Largest width.
  106.      */
  107.     int maxWidth = 0;
  108.  
  109.     /**
  110.      * Largest height.
  111.      */
  112.     int maxHeight = 0;
  113.  
  114.     /**
  115.      * Was there a problem loading the current image?
  116.      */
  117.     boolean imageLoadError = false;
  118.  
  119.     /**
  120.      * The directory or URL from which the images are loaded
  121.      */
  122.     URL imageSource = null;
  123.  
  124.     /**
  125.      * The directory or URL from which the sounds are loaded
  126.      */
  127.     URL soundSource = null;
  128.  
  129.     /**
  130.      * The thread animating the images.
  131.      */
  132.     Thread engine = null;
  133.  
  134.     /**
  135.      * The current loop slot - index into 'images.'
  136.      */
  137.     int frameNum;
  138.  
  139.     /**
  140.      * frameNum as an Object - suitable for use as a Hashtable key.
  141.      */
  142.     Integer frameNumKey;
  143.     
  144.     /**
  145.      * The current X position (for painting).
  146.      */
  147.     int xPos = 0;
  148.     
  149.     /**
  150.      * The current Y position (for painting).
  151.      */
  152.     int yPos = 0;
  153.     
  154.     /**
  155.      * The default number of milliseconds to wait between frames.
  156.      */
  157.     public static final int defaultPause = 3900;
  158.     
  159.     /**
  160.      * The global delay between images, which can be overridden by
  161.      * the PAUSE parameter.
  162.      */
  163.     int globalPause = defaultPause;
  164.  
  165.     /**
  166.      * Whether or not the thread has been paused by the user.
  167.      */
  168.     boolean userPause = false;
  169.  
  170.     /**
  171.      * Repeat the animation?  If false, just play it once.
  172.      */
  173.     boolean repeat;
  174.  
  175.     /**
  176.      * Load all images before starting display, or do it asynchronously?
  177.      */
  178.     boolean loadFirst;
  179.     
  180.     /**
  181.      * The offscreen image, used in double buffering
  182.      */
  183.     Image offScrImage;
  184.  
  185.     /**
  186.      * The offscreen graphics context, used in double buffering
  187.      */
  188.     Graphics offScrGC;
  189.  
  190.     /**
  191.      * A scratch offscreen graphics context, used to prefetch images.
  192.      */
  193.     Graphics prefetchGC;
  194.  
  195.     /**
  196.      * Can we paint yet?
  197.      */
  198.     boolean loaded = false;
  199.  
  200.     /**
  201.      * Was there an initialization error?
  202.      */
  203.     boolean error = false;
  204.  
  205.     /**
  206.      * What we call an image file in messages.
  207.      */
  208.     final static String imageLabel = "image";
  209.     
  210.     /**
  211.      * What we call a sound file in messages.
  212.      */
  213.     final static String soundLabel = "sound";
  214.     
  215.     /**
  216.      * Print silly debugging info?
  217.      */
  218.     final boolean debug = false;
  219.  
  220.     /**
  221.      * Applet info.
  222.      */
  223.     public String getAppletInfo() {
  224.     return "Animator by Herb Jellinek";
  225.     }
  226.  
  227.     /**
  228.      * Parameter info.
  229.      */
  230.     public String[][] getParameterInfo() {
  231.     String[][] info = {
  232.         {"imagesource",     "url",         "a directory"},
  233.         {"startup",     "url",         "displayed at startup"},
  234.         {"background",     "url",         "displayed as background"},
  235.         {"startimage",     "int",         "start index"},
  236.         {"endimage",     "int",         "end index"},
  237.         {"pause",             "int",         "milliseconds"},
  238.         {"pauses",             "ints",     "milliseconds"},
  239.         {"repeat",             "boolean",     "repeat or not"},
  240.         {"positions",    "coordinates",     "path"},
  241.         {"soundsource",    "url",         "audio directory"},
  242.         {"soundtrack",    "url",         "background music"},
  243.         {"sounds",        "urls",        "audio samples"},
  244.     };
  245.     return info;
  246.     }
  247.  
  248.     /**
  249.      * Print silly debugging info.
  250.      */
  251.     void dbg(String s) {
  252.     if (debug) {
  253.         System.out.println(s);
  254.     }
  255.     }
  256.  
  257.     final int setFrameNum(int newFrameNum) {
  258.     frameNumKey = new Integer(frameNum = newFrameNum);
  259.     return frameNum;
  260.     }
  261.     
  262.     public synchronized boolean imageUpdate(Image img, int infoFlags,
  263.                             int x, int y,
  264.                         int width, int height) {
  265.         if ((infoFlags & ERROR) != 0) {
  266.         imageLoadError = true;
  267.     }
  268.  
  269.     notifyAll();
  270.     return true;
  271.     }
  272.  
  273.     void updateMaxDims(Dimension dim) {
  274.     maxWidth = Math.max(dim.width, maxWidth);
  275.     maxHeight = Math.max(dim.height, maxHeight);
  276.     }
  277.  
  278.     /**
  279.      * Parse the IMAGES parameter.  It looks like
  280.      * 1|2|3|4|5, etc., where each number (item) names a source image.
  281.      *
  282.      * @return a Vector of (URL) image file names.
  283.      */
  284.     Vector parseImages(String attr)
  285.     throws MalformedURLException {
  286.     Vector result = new Vector(10);
  287.     for (int i = 0; i < attr.length(); ) {
  288.         int next = attr.indexOf('|', i);
  289.         if (next == -1) next = attr.length();
  290.         String file = attr.substring(i, next);
  291.         result.addElement(new URL(imageSource, "T"+file+".gif"));
  292.         i = next + 1;
  293.     }
  294.     return result;
  295.     }
  296.  
  297.     /**
  298.      * 'Prefetch' (draw) the image to really fetch it.
  299.      */
  300.     void prefetch(Image im) {
  301.     try {
  302.         prefetchGC.drawImage(im, 0, 0, null);
  303.     } catch (Exception e) {
  304.         e.printStackTrace();
  305.     }
  306.     }
  307.  
  308.     /**
  309.      * Fetch the images named in the argument, updating 
  310.      * maxWidth and maxHeight as we go.
  311.      * Is restartable.
  312.      *
  313.      * @param images a Vector of URLs
  314.      * @return URL of the first bogus file we hit, null if OK.
  315.      */
  316.     URL fetchImages(Vector images) {
  317.     for (int i = 0; i < images.size(); i++) {
  318.         Object o = images.elementAt(i);
  319.         if (o instanceof URL) {
  320.         URL url = (URL)o;
  321.         tellLoadingMsg(url, imageLabel);
  322.         Image im = getImage(url);
  323.         try {
  324.             updateMaxDims(getImageDimensions(im));
  325.             prefetch(im);
  326.         } catch (Exception e) {
  327.             return url;
  328.         }
  329.         images.setElementAt(im, i);
  330.         }
  331.     }
  332.     return null;
  333.     }
  334.  
  335.     /**
  336.      * Parse the SOUNDS parameter.  It looks like
  337.      * train.au||hello.au||stop.au, etc., where each item refers to a
  338.      * source image.  Empty items mean that the corresponding image
  339.      * has no associated sound.
  340.      *
  341.      * @return a Hashtable of SoundClips keyed to Integer frame numbers.
  342.      */
  343.     Hashtable parseSounds(String attr, Vector images)
  344.     throws MalformedURLException {
  345.     Hashtable result = new Hashtable();
  346.  
  347.     int imageNum = 0;
  348.     int numImages = images.size();
  349.     for (int i = 0; i < attr.length(); ) {
  350.         if (imageNum >= numImages) break;
  351.         
  352.         int next = attr.indexOf('|', i);
  353.         if (next == -1) next = attr.length();
  354.         
  355.         String sound = attr.substring(i, next);
  356.         if (sound.length() != 0) {
  357.         result.put(new Integer(imageNum),
  358.                new URL(soundSource, sound));
  359.         }
  360.         i = next + 1;
  361.         imageNum++;
  362.     }
  363.  
  364.     return result;
  365.     }
  366.  
  367.     /**
  368.      * Fetch the sounds named in the argument.
  369.      * Is restartable.
  370.      *
  371.      * @return URL of the first bogus file we hit, null if OK.
  372.      */
  373.     URL fetchSounds(Hashtable sounds) {
  374.     for (Enumeration e = sounds.keys() ; e.hasMoreElements() ;) {
  375.         Integer num = (Integer)e.nextElement();
  376.         Object o = sounds.get(num);
  377.         if (o instanceof URL) {
  378.         URL file = (URL)o;
  379.         tellLoadingMsg(file, soundLabel);
  380.         try {
  381.             sounds.put(num, getAudioClip(file));
  382.         } catch (Exception ex) {
  383.             return file;
  384.         }
  385.         }
  386.     }
  387.     return null;
  388.     }
  389.  
  390.     /**
  391.      * Parse the PAUSES parameter.  It looks like
  392.      * 1000|500|||750, etc., where each item corresponds to a
  393.      * source image.  Empty items mean that the corresponding image
  394.      * has no special duration, and should use the global one.
  395.      *
  396.      * @return a Hashtable of Integer pauses keyed to Integer
  397.      * frame numbers.
  398.      */
  399.     Hashtable parseDurations(String attr, Vector images) {
  400.     Hashtable result = new Hashtable();
  401.  
  402.     int imageNum = 0;
  403.     int numImages = images.size();
  404.     for (int i = 0; i < attr.length(); ) {
  405.         if (imageNum >= numImages) break;
  406.         
  407.         int next = attr.indexOf('|', i);
  408.         if (next == -1) next = attr.length();
  409.  
  410.         if (i != next - 1) {
  411.         int duration = Integer.parseInt(attr.substring(i, next));
  412.         result.put(new Integer(imageNum), new Integer(duration));
  413.         } else {
  414.         result.put(new Integer(imageNum),
  415.                new Integer(globalPause));
  416.         }
  417.         i = next + 1;
  418.         imageNum++;
  419.     }
  420.  
  421.     return result;
  422.     }
  423.  
  424.     /**
  425.      * Parse a String of form xxx@yyy and return a Point.
  426.      */
  427.     Point parsePoint(String s) throws ParseException {
  428.     int atPos = s.indexOf('@');
  429.     if (atPos == -1) throw new ParseException("Illegal position: "+s);
  430.     return new Point(Integer.parseInt(s.substring(0, atPos)),
  431.              Integer.parseInt(s.substring(atPos + 1)));
  432.     }
  433.  
  434.  
  435.     /**
  436.      * Parse the POSITIONS parameter.  It looks like
  437.      * 10@30|11@31|||12@20, etc., where each item is an X@Y coordinate
  438.      * corresponding to a source image.  Empty items mean that the
  439.      * corresponding image has the same position as the preceding one.
  440.      *
  441.      * @return a Hashtable of Points keyed to Integer frame numbers.
  442.      */
  443.     Hashtable parsePositions(String param, Vector images)
  444.     throws ParseException {
  445.     Hashtable result = new Hashtable();
  446.  
  447.     int imageNum = 0;
  448.     int numImages = images.size();
  449.     for (int i = 0; i < param.length(); ) {
  450.         if (imageNum >= numImages) break;
  451.         
  452.         int next = param.indexOf('|', i);
  453.         if (next == -1) next = param.length();
  454.  
  455.         if (i != next) {
  456.         result.put(new Integer(imageNum),
  457.                parsePoint(param.substring(i, next)));
  458.         }
  459.         i = next + 1;
  460.         imageNum++;
  461.     }
  462.  
  463.     return result;
  464.     }
  465.     
  466.     /**
  467.      * Get the dimensions of an image.
  468.      * @return the image's dimensions.
  469.      */
  470.     synchronized Dimension getImageDimensions(Image im)
  471.     throws ImageNotFoundException {
  472.     // Get the width of the image.
  473.     int width;
  474.     int height;
  475.     
  476.     while ((width = im.getWidth(this)) < 0) {
  477.         try {
  478.         wait();
  479.         } catch (InterruptedException e) { }
  480.         if (imageLoadError) {
  481.         throw new ImageNotFoundException(im.getSource());
  482.         }
  483.     }
  484.     
  485.     // Get the height of the image.
  486.     while ((height = im.getHeight(this)) < 0) {
  487.         try {
  488.         wait();
  489.         } catch (InterruptedException e) { }
  490.         if (imageLoadError) {
  491.         throw new ImageNotFoundException(im.getSource());
  492.         }
  493.     }
  494.  
  495.     return new Dimension(width, height);
  496.     }
  497.  
  498.     /**
  499.      * Stuff a range of image names into a Vector.
  500.      * @return a Vector of image URLs.
  501.      */
  502.     Vector prepareImageRange(int startImage, int endImage)
  503.     throws MalformedURLException {
  504.     Vector result = new Vector(Math.abs(endImage - startImage) + 1);
  505.     if (startImage > endImage) {
  506.         for (int i = startImage; i >= endImage; i--) {
  507.         result.addElement(new URL(imageSource, "T"+i+".gif"));
  508.         }
  509.     } else {
  510.         for (int i = startImage; i <= endImage; i++) {
  511.         result.addElement(new URL(imageSource, "T"+i+".gif"));
  512.         }
  513.     }
  514.     return result;
  515.     }
  516.  
  517.     
  518.     /**
  519.      * Initialize the applet.  Get parameters.
  520.      */
  521.     public void init() {
  522.  
  523.     prefetchGC = createImage(1, 1).getGraphics();
  524.  
  525.     try {
  526.         String param = getParameter("IMAGESOURCE");    
  527.         imageSource = (param == null) ? getDocumentBase() : new URL(getDocumentBase(), param + "/");
  528.         dbg("IMAGESOURCE = "+param);
  529.     
  530.         param = getParameter("PAUSE");
  531.         globalPause =
  532.         (param != null) ? Integer.parseInt(param) : defaultPause;
  533.         dbg("PAUSE = "+param);
  534.  
  535.         param = getParameter("REPEAT");
  536.         repeat = (param == null) ? true : (param.equalsIgnoreCase("yes") ||
  537.                            param.equalsIgnoreCase("true"));
  538.  
  539.         int startImage = 1;
  540.         int endImage = 1;
  541.         param = getParameter("ENDIMAGE");
  542.         dbg("ENDIMAGE = "+param);
  543.         if (param != null) {
  544.         endImage = Integer.parseInt(param);
  545.         param = getParameter("STARTIMAGE");
  546.         dbg("STARTIMAGE = "+param);
  547.         if (param != null) {
  548.             startImage = Integer.parseInt(param);
  549.         }
  550.         images = prepareImageRange(startImage, endImage);
  551.         } else {
  552.         param = getParameter("STARTIMAGE");
  553.         dbg("STARTIMAGE = "+param);
  554.         if (param != null) {
  555.             startImage = Integer.parseInt(param);
  556.             images = prepareImageRange(startImage, endImage);
  557.         } else {
  558.             param = getParameter("IMAGES");
  559.             dbg("IMAGES = "+param);
  560.             if (param == null) {
  561.             showStatus("No legal IMAGES, STARTIMAGE, or ENDIMAGE "+
  562.                    "specified.");
  563.             return;
  564.             } else {
  565.             images = parseImages(param);
  566.             }
  567.         }
  568.         }
  569.  
  570.         param = getParameter("BACKGROUND");
  571.         dbg("BACKGROUND = "+param);
  572.         if (param != null) {
  573.         backgroundImageURL = new URL(imageSource, param);
  574.         }
  575.  
  576.         param = getParameter("STARTUP");
  577.         dbg("STARTUP = "+param);
  578.         if (param != null) {
  579.         startUpImageURL = new URL(imageSource, param);
  580.         }
  581.  
  582.         param = getParameter("SOUNDSOURCE");
  583.         soundSource = (param == null) ? imageSource : new URL(getDocumentBase(), param + "/");
  584.         dbg("SOUNDSOURCE = "+param);
  585.     
  586.         param = getParameter("SOUNDS");
  587.         dbg("SOUNDS = "+param);
  588.         if (param != null) {
  589.         sounds = parseSounds(param, images);
  590.         }
  591.  
  592.         param = getParameter("PAUSES");
  593.         dbg("PAUSES = "+param);
  594.         if (param != null) {
  595.         durations = parseDurations(param, images);
  596.         }
  597.  
  598.         param = getParameter("POSITIONS");
  599.         dbg("POSITIONS = "+param);
  600.         if (param != null) {
  601.         positions = parsePositions(param, images);
  602.         }
  603.  
  604.         param = getParameter("SOUNDTRACK");
  605.         dbg("SOUNDTRACK = "+param);
  606.         if (param != null) {
  607.         soundtrackURL = new URL(soundSource, param);
  608.         }
  609.     } catch (MalformedURLException e) {
  610.         showParseError(e);
  611.     } catch (ParseException e) {
  612.         showParseError(e);
  613.     }
  614.     
  615.  
  616.  
  617.     setFrameNum(0);
  618.     }
  619.  
  620.     void tellLoadingMsg(String file, String fileType) {
  621.     showStatus("Animator: loading "+fileType+" "+abridge(file, 20));
  622.     }
  623.  
  624.     void tellLoadingMsg(URL url, String fileType) {
  625.     tellLoadingMsg(url.toExternalForm(), fileType);
  626.     }
  627.  
  628.     void clearLoadingMessage() {
  629.     showStatus("");
  630.     }
  631.     
  632.     /**
  633.      * Cut the string down to length=len, while still keeping it readable.
  634.      */
  635.     static String abridge(String s, int len) {
  636.     String ellipsis = "...";
  637.  
  638.     if (len >= s.length()) {
  639.         return s;
  640.     }
  641.  
  642.     int trim = len - ellipsis.length();
  643.     return s.substring(0, trim / 2)+ellipsis+
  644.         s.substring(s.length() - trim / 2);
  645.     }
  646.     
  647.     void loadError(URL badURL, String fileType) {
  648.     String errorMsg = "Animator: Couldn't load "+fileType+" "+
  649.         badURL.toExternalForm();
  650.     showStatus(errorMsg);
  651.     System.err.println(errorMsg);
  652.     error = true;
  653.     repaint();
  654.     }
  655.  
  656.     void showParseError(Exception e) {
  657.     String errorMsg = "Animator: Parse error: "+e;
  658.     showStatus(errorMsg);
  659.     System.err.println(errorMsg);
  660.     error = true;
  661.     repaint();
  662.     }
  663.  
  664.     void startPlaying() {
  665.     if (soundtrack != null) {
  666.         soundtrack.loop();
  667.     }
  668.     }
  669.  
  670.     void stopPlaying() {
  671.     if (soundtrack != null) {
  672.         soundtrack.stop();
  673.     }
  674.     }
  675.  
  676.     /**
  677.      * Run the animation. This method is called by class Thread.
  678.      * @see java.lang.Thread
  679.      */
  680.     public void run() {
  681.     Thread me = Thread.currentThread();
  682.  
  683.     me.setPriority(Thread.MIN_PRIORITY);
  684.  
  685.     if (! loaded) {
  686.         try {
  687.         // ... to do a bunch of loading.
  688.         if (startUpImageURL != null) {
  689.             tellLoadingMsg(startUpImageURL, imageLabel);
  690.             startUpImage = getImage(startUpImageURL);
  691.             try {
  692.             updateMaxDims(getImageDimensions(startUpImage));
  693.             prefetch(startUpImage);
  694.             repaint();
  695.  
  696.             } catch (Exception e) {
  697.             loadError(startUpImageURL, "start-up image");
  698.             }
  699.             resize(maxWidth, maxHeight);
  700.             repaint();
  701.         }
  702.  
  703.         if (backgroundImageURL != null) {
  704.             tellLoadingMsg(backgroundImageURL, imageLabel);
  705.             backgroundImage = getImage(backgroundImageURL);
  706.             try {
  707.             updateMaxDims(
  708.                getImageDimensions(backgroundImage));
  709.             prefetch(backgroundImage);
  710.             repaint();
  711.             } catch (Exception e) {
  712.             loadError(backgroundImageURL, "background image");
  713.             }
  714.         }
  715.  
  716.         URL badURL = fetchImages(images);
  717.         if (badURL != null) {
  718.             loadError(badURL, imageLabel);
  719.             return;
  720.         }
  721.  
  722.         if (soundtrackURL != null && soundtrack == null) {
  723.             tellLoadingMsg(soundtrackURL, imageLabel);
  724.             soundtrack = getAudioClip(soundtrackURL);
  725.             if (soundtrack == null) {
  726.             loadError(soundtrackURL, "soundtrack");
  727.             return;
  728.             }
  729.         }
  730.  
  731.         if (sounds != null) {
  732.             badURL = fetchSounds(sounds);
  733.             if (badURL != null) {
  734.             loadError(badURL, soundLabel);
  735.             return;
  736.             }
  737.         }
  738.  
  739.         clearLoadingMessage();
  740.  
  741.         offScrImage = createImage(maxWidth, maxHeight);
  742.         offScrGC = offScrImage.getGraphics();
  743.         offScrGC.setColor(Color.lightGray);
  744.  
  745.         resize(maxWidth, maxHeight);
  746.         loaded = true;
  747.         error = false;
  748.         } catch (Exception e) {
  749.         error = true;
  750.         e.printStackTrace();
  751.         }
  752.     }
  753.  
  754.     if (userPause) {
  755.         return;
  756.     }
  757.  
  758.     if (repeat || frameNum < images.size()) {
  759.         startPlaying();
  760.     }
  761.  
  762.     try {
  763.         if (images.size() > 1) {
  764.         while (maxWidth > 0 && maxHeight > 0 && engine == me) {
  765.             if (frameNum >= images.size()) {
  766.             if (!repeat) {
  767.                 return;
  768.             }
  769.             setFrameNum(0);
  770.             }
  771.             repaint();
  772.  
  773.             if (sounds != null) {
  774.             AudioClip clip =
  775.                 (AudioClip)sounds.get(frameNumKey);
  776.             if (clip != null) {
  777.                 clip.play();
  778.             }
  779.             }
  780.  
  781.             try {
  782.             Integer pause = null;
  783.             if (durations != null) {
  784.                 pause = (Integer)durations.get(frameNumKey);
  785.             }
  786.             if (pause == null) {
  787.                 Thread.sleep(globalPause);
  788.             } else {
  789.                 Thread.sleep(pause.intValue());
  790.             }
  791.             } catch (InterruptedException e) {
  792.             // Should we do anything?
  793.             }
  794.             setFrameNum(frameNum+1);
  795.         }
  796.         }
  797.     } finally {
  798.         stopPlaying();
  799.     }
  800.     }
  801.  
  802.     /**
  803.      * Paint the current frame.
  804.      */
  805.     public void paint(Graphics g) {
  806.     if (error || !loaded) {
  807.         if (startUpImage != null) {
  808.         g.drawImage(startUpImage, 0, 0, this);
  809.         } else {
  810.         if (backgroundImage != null) {
  811.             g.drawImage(backgroundImage, 0, 0, this);
  812.         } else {
  813.             g.clearRect(0, 0, maxWidth, maxHeight);
  814.         }
  815.         }
  816.     } else {
  817.         if ((images != null) && (images.size() > 0)) {
  818.         if (frameNum < images.size()) {
  819.             if (backgroundImage == null) {
  820.             offScrGC.fillRect(0, 0, maxWidth, maxHeight);
  821.             } else {
  822.             offScrGC.drawImage(backgroundImage, 0, 0, this);
  823.             }
  824.  
  825.             Image image = (Image)images.elementAt(frameNum);
  826.             Point pos = null;
  827.             if (positions != null) {
  828.             pos = (Point)positions.get(frameNumKey);
  829.             }
  830.             if (pos != null) {
  831.             xPos = pos.x;
  832.             yPos = pos.y;
  833.             }
  834.             offScrGC.drawImage(image, xPos, yPos, this);
  835.             g.drawImage(offScrImage, 0, 0, this);
  836.         } else {
  837.             // no more animation, but need to draw something
  838.             dbg("No more animation; drawing last image.");
  839.             g.drawImage((Image)images.lastElement(), 0, 0, this);
  840.         }
  841.         }
  842.     }
  843.     }
  844.  
  845.     /**
  846.      * Start the applet by forking an animation thread.
  847.      */
  848.     public void start() {
  849.     if (engine == null) {
  850.         engine = new Thread(this);
  851.         engine.start();
  852.     }
  853.     }
  854.  
  855.     /**
  856.      * Stop the insanity, um, applet.
  857.      */
  858.     public void stop() {
  859.     if (engine != null && engine.isAlive()) {
  860.         engine.stop();
  861.     }
  862.     engine = null;
  863.     }
  864.  
  865.     /**
  866.      * Pause the thread when the user clicks the mouse in the applet.
  867.      * If the thread has stopped (as in a non-repeat performance),
  868.      * restart it.
  869.      */
  870.     public boolean handleEvent(Event evt) {
  871.     if (evt.id == Event.MOUSE_DOWN) {
  872.         if (loaded) {
  873.         if (engine != null && engine.isAlive()) {
  874.             if (userPause) {
  875.             engine.resume();
  876.             startPlaying();
  877.             } else {
  878.             engine.suspend();
  879.             stopPlaying();
  880.             }
  881.             userPause = !userPause;
  882.         } else {
  883.             userPause = false;
  884.             setFrameNum(0);
  885.             engine = new Thread(this);
  886.             engine.start();
  887.         }
  888.         }
  889.         return true;
  890.     } else {        
  891.         return super.handleEvent(evt);
  892.     }
  893.     }
  894.     
  895. }
  896.  
  897.  
  898. class ParseException extends Exception {
  899.     ParseException(String s) {
  900.     super(s);
  901.     }
  902. }
  903.  
  904. class ImageNotFoundException extends Exception {
  905.     ImageNotFoundException(ImageProducer source) {
  906.     super(source+"");
  907.     }
  908. }
  909.  
  910.